package com.jeasy.resolver;

import javax.servlet.ServletRequest;

import java.lang.annotation.Annotation;
import java.util.Enumeration;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.ExtendedServletRequestDataBinder;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import lombok.extern.slf4j.Slf4j;

/**
 * @author taomk
 * @version 1.0
 * @since 15-8-4 下午4:45
 */
@Slf4j
public class MethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(JsonObj.class);
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		JsonObj jsonObj = parameter.getParameterAnnotation(JsonObj.class);

		String alias = getAlias(jsonObj, parameter);

		// 拿到obj, 先从ModelAndViewContainer中拿，若没有则创建参数类型的实例
		Object obj = (mavContainer.containsAttribute(alias)) ? mavContainer.getModel().get(alias) : createAttribute(parameter.getParameterType());

		// 获得WebDataBinder，这里的具体WebDataBinder是ExtendedServletRequestDataBinder
		WebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias);

		Object target = binder.getTarget();

		if(target != null) {
			// 绑定参数
			bindParameters(webRequest, binder, alias);
			// JSR303 验证
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors()) {
				if (isBindExceptionRequired(parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
		}

		if(jsonObj.show()) {
			mavContainer.addAttribute(alias, target);
		}
		return target;
	}

	private Object createAttribute(Class<?> parameterType) {
		return BeanUtils.instantiateClass(parameterType);
	}

	private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) {
		MockHttpServletRequest newRequest = new MockHttpServletRequest();

		ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
		Enumeration enu = servletRequest.getParameterNames();
		while(enu.hasMoreElements()) {
			String paramName = (String) enu.nextElement();
			String paramVal = request.getParameter(paramName);
			if (paramName.equals(alias)) {
				JsonObject jsonObject =  new JsonParser().parse(paramVal).getAsJsonObject();
				bindJsonToRequest(newRequest, jsonObject, StringUtils.EMPTY);
				continue;
			}
			newRequest.setParameter(paramName, paramVal);
		}

		((ExtendedServletRequestDataBinder)binder).bind(newRequest);
	}

	private void bindJsonToRequest(MockHttpServletRequest newRequest, JsonElement jsonElement, String key) {
		if (jsonElement.isJsonPrimitive()) {
			newRequest.setParameter(key, jsonElement.getAsString());
		} else if (jsonElement.isJsonObject()) {
			key = StringUtils.isNotBlank(key) ? key + "." : key;
			for (Map.Entry<String, JsonElement> entry : jsonElement.getAsJsonObject().entrySet()) {
				bindJsonToRequest(newRequest, entry.getValue(), key + entry.getKey());
			}
		} else if (jsonElement.isJsonArray()) {
			JsonArray jsonArray = jsonElement.getAsJsonArray();
			for (int i = 0; i < jsonArray.size(); i++) {
				bindJsonToRequest(newRequest, jsonArray.get(i), key + "[" + i + "]");
			}
		} else {
			newRequest.setParameter(key, StringUtils.EMPTY);
		}
	}

	private void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation annotation : annotations) {
			if (annotation.annotationType().getSimpleName().startsWith("Valid")) {
				Object hints = AnnotationUtils.getValue(annotation);
				binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				break;
			}
		}
	}

	protected boolean isBindExceptionRequired(MethodParameter parameter) {
		int i = parameter.getParameterIndex();
		Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
		boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));

		return !hasBindingResult;
	}

	/**
	 * 获取对象参数的简称
	 * @param jsonObj
	 * @param parameter
	 * @return
	 */
	private String getAlias(JsonObj jsonObj, MethodParameter parameter) {
		// 得到JsonObj的属性key，也就是对象参数的简称
		String alias = jsonObj.key();
		if(alias == null || StringUtils.isBlank(alias)) {
			// 如果简称为空，取对象简称的首字母小写开头
			alias = parameter.getParameterName();
		}
		return alias;
	}
}
